pdf檔也是常見的檔案格式,這次的範例是想嘗試在Spring boot的環境中,查詢實際的DB,將資料整理後匯出pdf。
想模擬的情境是學校學生成績的相關報表。
以SQL查詢Student Table,確認是我們要的資料
SELECT * FROM ithome2024.student;
在MySQl中看到原始資料大概有這些
我常使用JPA搭配Querydsl,先建立映射對象Entity
@Entity
@Table(name = "student", schema = "ithome2024")
public class StudentEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "Student_Id")
private Integer studentId;
@Column(name = "First_Name")
private String firstName;
@Column(name = "Last_Name")
private String lastName;
@Column(name = "Gender")
private String gender;
@Column(name = "Grade")
private String grade;
@Column(name = "Department_Id")
private Integer departmentId;
}
Querydsl很像SQL語法,還可以透過Projections.bean
自動將查詢結果的列對應到DTO的屬性上。
@Repository
public class JasperReportDemoDaoImpl implements IJasperReportDemoDao {
@Autowired
private JPQLQueryFactory queryFactory;
@Override
public List<StudentAndDepartmentDto> getStudentAndDepartmentData() {
QStudentEntity qStudent = QStudentEntity.studentEntity;
QDepartmentEntity qDepartment = QDepartmentEntity.departmentEntity;
return queryFactory.select(Projections
.bean(StudentAndDepartmentDto.class,
qStudent.studentId, qStudent.firstName, qStudent.lastName,
qStudent.gender, qStudent.grade,
qDepartment.departmentId, qDepartment.departmentName,
qDepartment.departmentDesc))
.from(qStudent)
.innerJoin(qDepartment)
.on(qStudent.departmentId.eq(qDepartment.departmentId))
.fetch();
}
}
由於這次情境很單純,Service沒有什麼邏輯要處理,僅是做一些資料轉換
@Service
public class ReportDemoServiceImpl implements IReportDemoService {
@Autowired
private IJasperReportDemoDao jasperReportDemoDao;
@Override
public List<StudentDataReportModel> getStudentAndDepartmentData() {
List<StudentDataReportModel> studentDataReportModelList = null;
try {
studentDataReportModelList = Optional
.of(jasperReportDemoDao.getStudentAndDepartmentData())
.orElse(new ArrayList<>())
.stream().map(StudentDataReportModel::new)
.collect(Collectors.toList());
} catch (Exception e) {
throw new RuntimeException(e);
}
return studentDataReportModelList;
}
}
資料轉換的部分放在Model的建構子中
@Data
@AllArgsConstructor
@NoArgsConstructor
public class StudentDataReportModel {
private Integer studentId;
private String fullName;
private String gender;
private String grade;
private String departmentDesc;
public StudentDataReportModel(StudentAndDepartmentDto dto) {
this.studentId = dto.getStudentId();
this.fullName = dto.getFirstName() + " " + dto.getLastName();
this.gender = "Male".equals(dto.getGender()) ? "男" : "女";
this.grade = dto.getGrade();
this.departmentDesc = dto.getDepartmentDesc();
}
}
List<StudentDataReportModel>
就是這次要放進報表的DataSource// 1. 查詢學生基本資料
List<StudentDataReportModel> studentDataReportModelList =
reportDemoService.getStudentAndDepartmentData();
// 2. 設定報表參數
Map<String, Object> parametersMap =
this.getStudentDataParameters(studentDataReportModelList);
private Map<String, Object> getStudentDataParameters(
List<StudentDataReportModel> studentDataReportModelList) {
Map<String, Object> parameters = new HashMap<>();
LocalDate localDate = new Date().toInstant()
.atZone(ZoneId.systemDefault()).toLocalDate();
parameters.put("date", localDate
.format(DateTimeFormatter.ofPattern("yyyy年MM月dd日")));
parameters.put("studentNum", studentDataReportModelList.size());
return parameters;
}
public class ExportReportUtil {
// 以DataSource、報表路徑、parametersMap作為參數,在Util中處理報表生命週期
public static byte[] templateToPdfByteSimple(List dataSourceList,
String reportPath, Map<String, Object> parametersMap
) throws Exception {
try {
// 以JasperCompileManager將jrxml模板編譯成jasper文件
JasperReport jasperReport = JasperCompileManager
.compileReport(ExportReportUtil
.class.getResourceAsStream(reportPath));
// 將Java集合資料來源與Jasper報表進行綁定
JRDataSource dataSource =
new JRBeanCollectionDataSource(dataSourceList, true);
// 將資料填入報表
JasperPrint print = JasperFillManager
.fillReport(jasperReport, parametersMap, dataSource);
// 匯出為pdf
return JasperExportManager.exportReportToPdf(print);
} catch (Exception e) {
throw new Exception();
}
}
}
方法中宣告模板路徑,並將第一步的DataSource、第二步的parametersMap都作為參數傳入templateToPdfByteSimple方法
// 3.匯出excel byte[]
byte[] bytes = null;
try {
String reportPath = "/Report/Jasper/StudentDataReport.jrxml";
bytes = ExportReportUtil
.templateToPdfByteSimple(studentDataReportModelList,
reportPath, parametersMap);
} catch (Exception e) {
throw new RuntimeException(e);
}
// 4.設定檔案名稱
String encodedFilename = null;
try {
encodedFilename = URLEncoder
.encode("學生與科系資料." + fileType, StandardCharsets.UTF_8.name());
} catch (Exception e) {
throw new RuntimeException(e);
}
最後打開匯出的pdf,但越看越不對勁...
中文字都不見了!
google之後發現這個問題存在已久,也有不少解決方案,就在下一篇說明吧